<?php
/**
 *
 * Author: Lyu
 * Date: 2022/4/11
 * Time: 15:38
 */

namespace App\Library\Service;

use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use Psr\Http\Message\ResponseInterface;

class HttpClient
{
    /**
     * 请求地址
     * @var string
     */
    protected string $base_uri = '';

    protected Client $Http;

    /** @var array Map of standard HTTP status code/reason phrases */
    private static array $phrases = [
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-status',
        208 => 'Already Reported',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => 'Switch Proxy',
        307 => 'Temporary Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Time-out',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Large',
        415 => 'Unsupported Media Type',
        416 => 'Requested range not satisfiable',
        417 => 'Expectation Failed',
        418 => 'I\'m a teapot',
        422 => 'Unprocessable Entity',
        423 => 'Locked',
        424 => 'Failed Dependency',
        425 => 'Unordered Collection',
        426 => 'Upgrade Required',
        428 => 'Precondition Required',
        429 => 'Too Many Requests',
        431 => 'Request Header Fields Too Large',
        451 => 'Unavailable For Legal Reasons',
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Time-out',
        505 => 'HTTP Version not supported',
        506 => 'Variant Also Negotiates',
        507 => 'Insufficient Storage',
        508 => 'Loop Detected',
        511 => 'Network Authentication Required',
    ];
    /**
     * 请求时的主体部分body
     * @var string
     */
    protected string $body = '';
    /**
     * 请求超时的秒数，使用 0 无限期的等待
     * @var int
     */
    protected int $timeout = 60;
    /**
     * HTTP请求头
     * @var array
     */
    protected array $header = [];
    /**
     * 要添加到请求的查询字符串的关联数组（拼接到URL上的）
     * @var array
     */
    protected array $query = [];
    /**
     * @var array
     */
    protected array $auth = [];
    protected string $method = '';

    /**
     * @var CookieJar
     */
    protected CookieJar $cookies;

    /**
     * POST请求时表单字段
     * @var array
     */
    protected array $form_params = [];

    /**
     * 请求中间件
     * @var array
     */
    protected array $middlewares = [];
    /**
     * @var HandlerStack
     */
    protected HandlerStack $handlerStack;
    /**
     * @var ResponseInterface
     */
    protected ResponseInterface $response;
    /**
     * @var RequestException
     */
    protected RequestException $exception;
    /**
     * Guzzle client default settings.
     *
     * @var array
     */
    protected static array $defaults = [
        'curl' => [
            CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
        ],
    ];

    protected array $option = [];

    public function __construct()
    {

    }

    /**
     * @param string $base_uri
     * @return $this
     */
    public function setBaseUri(string $base_uri)
    {
        $this->base_uri = $base_uri;
        return $this;
    }

    /**
     * @param string $pathname
     * @param string $method
     * @return $this
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function request($pathname = '', $method = 'POST')
    {
        $options = [
            'base_uri' => $this->base_uri,
            'timeout' => $this->timeout,
            'verify' => false,
            'handler' => $this->getHandlerStack(),
        ];

        if ($this->body != '') {
            $options['body'] = $this->body;
        }
        if ($this->auth) {
            $options['auth'] = $this->auth;
        }
        if ($this->header) {
            $options['headers'] = $this->header;
        }
        if ($this->query) {
            $options['query'] = $this->query;
        }
        if ($this->form_params) {
            $options['form_params'] = $this->form_params;
        }
        $this->option = $options;
        $this->Http = new Client();
        $this->method = $method;
        try {
            $this->response = $this->Http->request($method, $pathname, $options);
        } catch (RequestException $exception) {
            $this->exception = $exception;
        }
        return $this;
    }


    /**
     * Add a middleware.
     *
     * @param callable $middleware
     * @param null|string $name
     *
     * @return $this
     */
    public function pushMiddleware(callable $middleware, string $name = null)
    {
        if (!is_null($name)) {
            $this->middlewares[$name] = $middleware;
        } else {
            array_push($this->middlewares, $middleware);
        }
        return $this;
    }

    /**
     * Build a handler stack.
     *
     * @return \GuzzleHttp\HandlerStack
     */
    public function getHandlerStack(): HandlerStack
    {
        if (isset($this->handlerStack)) {
            return $this->handlerStack;
        }

        $this->handlerStack = HandlerStack::create();

        foreach ($this->middlewares as $name => $middleware) {
            $this->handlerStack->push($middleware, $name);
        }
        return $this->handlerStack;
    }

    public function bodyToArray()
    {
        if (isset($this->exception)) {
            $code = $this->exception->getCode();
            $messages = $this->exception->getMessage();
            if (!empty($code)) {
                $messagesArr = explode("\n", $messages);
                $msg = $messagesArr[0];
                return ['status' => 'fail', 'code' => $code, 'error' => self::$phrases[$code], 'msg' => $msg, 'message' => $messagesArr[1]];
            } else {
                $content = $this->exception->getHandlerContext();
                $error_code = $content['errno'];
                $error_msg = $content['error'];
                return ['status' => 'fail', 'code' => $error_code, 'error' => $error_msg, 'msg' => $messages, 'message' => $error_msg];
            }
        } else {
            $status_code = $this->response->getStatusCode();
            $content = $this->response->getBody()->getContents();
            return ['status' => 'success', 'status_code' => $status_code, 'content' => $content];
        }
    }

    /**
     * Build to array.
     *
     * @return array
     */
    public function toArray()
    {
        if (isset($this->exception)) {
            $code = $this->exception->getCode();
            $messages = $this->exception->getMessage();
            if (!empty($code)) {
                $messagesArr = explode("\n", $messages);
                $msg = $messagesArr[0];
                $data = json_decode($messagesArr[1], true, 512, JSON_BIGINT_AS_STRING);
                if (JSON_ERROR_NONE !== json_last_error()) {
                    $data = [];
                }
                return ['status' => 'fail', 'code' => $code, 'error' => self::$phrases[$code], 'msg' => $msg, 'data' => $data];
            } else {
                $content = $this->exception->getHandlerContext();
                $error_code = $content['errno'];
                $error_msg = $content['error'];
                return ['status' => 'fail', 'code' => $error_code, 'error' => $error_msg, 'msg' => $messages];
            }
        } else {
            $content = $this->response->getBody();
            $content_type = implode(', ', $this->response->getHeader('Content-Type'));
            if (false !== stripos($content_type, 'xml') || 0 === stripos($content, '<xml')) {
                $data = (array)self::parseXml($content);
                return ['status' => 'success', 'code' => 0, 'error' => '', 'msg' => '', 'data' => $data];
            }
            $array = json_decode($content, true, 512, JSON_BIGINT_AS_STRING);
            if (JSON_ERROR_NONE === json_last_error()) {
                return ['status' => 'success', 'code' => 0, 'error' => '', 'msg' => '', 'data' => $array];
            } else {
                return ['status' => 'success', 'code' => 0, 'error' => '', 'msg' => '', 'data' => $content];
            }
        }
    }

    public function parseJson($content)
    {
        return json_decode($content, true, 512, JSON_BIGINT_AS_STRING);
    }

    public function parseXml($content)
    {
        $backup = libxml_disable_entity_loader(true);

        $result = simplexml_load_string($content, 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS);

        libxml_disable_entity_loader($backup);

        return $result;
    }

    /**
     * @return array
     */
    public function getMiddlewares(): array
    {
        return $this->middlewares;
    }

    /**
     * @return ResponseInterface
     */
    public function getResponse()
    {
        return $this->response;
    }


    /**
     * 获取body
     * @return string
     */
    public function getRequestBody()
    {
        return $this->body;
    }

    /**
     * @return array
     */
    public function getOption(): array
    {
        return $this->option;
    }

    /**
     * @param array $header
     * @return $this
     */
    public function setHeader(array $header)
    {
        $this->header = $header;
        return $this;
    }

    /**
     * @param string $body
     * @return $this
     */
    public function setBody(string $body)
    {
        $this->body = $body;
        return $this;
    }

    /**
     * @param array $query
     * @return $this
     */
    public function setQuery(array $query)
    {
        $this->query = $query;
        return $this;
    }

    /**
     * @param array $auth
     * @return $this
     */
    public function setAuth(array $auth)
    {
        $this->auth = $auth;
        return $this;
    }

    /**
     * @param CookieJar $cookies
     * @return $this
     */
    public function setCookies(CookieJar $cookies)
    {
        $this->cookies = $cookies;
        return $this;
    }

    /**
     * @param array $form_params
     * @return $this
     */
    public function setFormParams(array $form_params)
    {
        $this->form_params = $form_params;
        return $this;
    }

    /**
     * @param int $timeout
     * @return $this
     */
    public function setTimeout(int $timeout)
    {
        $this->timeout = (int)$timeout;
        return $this;
    }
}
